﻿using System;
using System.Linq;

namespace BSharp.Compiler.Emit
{
	public class BCompiler : AstVisitor
	{

		private int topRegister = 0;

		private FunctionBuilder currentFunction;
		private ObjectCompilationUnit compilationUnit;

		private BSharpContext context;
		private SymbolTable symbolTable = new SymbolTable ();

		public BCompiler (BSharpContext context, SymbolTable symTab)
		{
			this.context = context;
			symbolTable = symTab;
		}

		public ObjectCompilationUnit Compile (AstNode root)
		{
			compilationUnit = new ObjectCompilationUnit ();
			root.VisitChildren (this);
			return compilationUnit;
		}

		public override void Accept (FunctionDeclaration declaration)
		{
			symbolTable.EnterScope ();
			currentFunction = new FunctionBuilder (declaration.Name);
			Console.WriteLine (symbolTable.GetSymbol (declaration.Name));
			Console.ReadLine ();
			currentFunction.StackAlloc (symbolTable.GetSymbol (declaration.Name) * 2);

			currentFunction.Emit (new Enter (currentFunction));

			foreach (string parameter in declaration.Parameters) {
				currentFunction.Emit (new PopArgument (PushRegister (), symbolTable.AddSymbol (parameter)));
				currentFunction.Emit (new StoreLocal (PopRegister (), symbolTable.GetSymbol (parameter)));
			}

			declaration.VisitChildren (this);

			symbolTable.ExitScope ();

			currentFunction.Emit (new Ret (currentFunction));

			compilationUnit.AddFunction (new FunctionCompilationUnit (currentFunction.GetInstructions ()));
		}

		public override void Accept (CodeBlock codeblock)
		{
			symbolTable.EnterScope ();
			codeblock.VisitChildren (this);
			symbolTable.ExitScope ();
		}

		public override void Accept (AutoDeclaration autoDeclaration)
		{
			symbolTable.AddSymbol (autoDeclaration.Name);

			if (autoDeclaration.Value != null) {
				autoDeclaration.Value.Visit (this);
				currentFunction.Emit (new StoreLocal (PopRegister (), symbolTable.GetSymbol (autoDeclaration.Name)));
			}

		}

		public override void Accept (VectorDeclaration vectorDeclaration)
		{
			symbolTable.AddSymbol (vectorDeclaration.Name);
			int offset = currentFunction.StackAlloc (vectorDeclaration.Size);
			currentFunction.Emit (new StackAlloc (symbolTable.GetSymbol (vectorDeclaration.Name), offset));
		}

		public override void Accept (IfStatement ifStatement)
		{
			ifStatement.Condition.Visit (this);

			int r = PopRegister ();

			currentFunction.Emit (new And (r, r));

			Symbol elseSymbol = new Symbol ();
			Symbol endSymbol = new Symbol ();
			currentFunction.Emit (new Bz (elseSymbol.CreateReference ()));

			ifStatement.Body.Visit (this);

			currentFunction.Emit (new Bra (endSymbol.CreateReference ()));

			currentFunction.Emit (new DefineLabel (elseSymbol));

			ifStatement.ElseBody.Visit (this);

			currentFunction.Emit (new DefineLabel (endSymbol));
		}

		public override void Accept (ExpressionStatement exprStmt)
		{
			exprStmt.Expression.Visit (this);
			PopRegister ();
		}

		public override void Accept (CallExpression callExpression)
		{
			if (callExpression.Target is IdentifierExpression) {
				IdentifierExpression target = callExpression.Target as IdentifierExpression;
				callExpression.Arguments.Visit (this);
				currentFunction.Emit (new CallDirect (new SymbolReference (target.Name), callExpression.Arguments.Arguments.Count ()));
			} else {
				callExpression.Arguments.Visit (this);
				callExpression.Target.Visit (this);
				currentFunction.Emit (new CallIndirect (PopRegister (), 0));
				PushRegister ();
			}
			PushRegister ();
		}

		public override void Accept (ArgumentList argumentList)
		{
			AstNode[] elements = argumentList.Arguments.ToArray ();

			for (int i = elements.Length - 1; i >= 0; i--) {
				elements [i].Visit (this);
				currentFunction.Emit (new PushArgument (PopRegister ()));
			}
		}

		public override void Accept (BinaryExpression binaryExpression)
		{
			int r1, r2;
			switch (binaryExpression.Operation) {
			case BinaryOperation.GreaterThan:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new TestIfGreaterThan (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.LessThan:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new TestIfLessThan (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.Equal:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new TestIfEqual (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.NotEqual:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new TestIfNotEqual (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.GreaterThanOrEqu:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new TestIfGreaterThanOrEqual (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.And:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new And (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.Add:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new Add (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.Sub:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new Sub (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.Mul:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new Mul (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.Div:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new Div (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.Mod:
				binaryExpression.Left.Visit (this);
				binaryExpression.Right.Visit (this);
				r1 = PopRegister ();
				r2 = PopRegister ();
				currentFunction.Emit (new Mod (r2, r1));
				PushRegister ();
				break;
			case BinaryOperation.Assign:
				CompileAssignment (binaryExpression.Left, binaryExpression.Right);
				break;
			}
		}

		private void CompileAssignment (AstNode source, AstNode value)
		{
			if (source is IdentifierExpression) {
				IdentifierExpression identExpr = source as IdentifierExpression;
				value.Visit (this);
				int vr = PopRegister ();
				currentFunction.Emit (new StoreLocal (vr, symbolTable.GetSymbol (identExpr.Name)));
				PushRegister ();
			} else if (source is UnaryExpression) {
				UnaryExpression unaryOp = source as UnaryExpression;

				if (unaryOp.Operation == UnaryOperation.Derefence) {
					value.Visit (this);
					unaryOp.Value.Visit (this);
					int dR = PopRegister ();
					int sR = PopRegister ();
					currentFunction.Emit (new StoreWord (dR, sR));
					PushRegister ();
				} else {
					/* Create error */
				}
			} else if (source is IndexerExpression) {
				IndexerExpression indexer = source as IndexerExpression;

				value.Visit (this);
				indexer.Target.Visit (this);
				indexer.Index.Visit (this);

				int iR = PopRegister ();
				int tR = PopRegister ();

				int vR = PopRegister ();

				currentFunction.Emit (new Muli (iR, 2));
				currentFunction.Emit (new Add (tR, iR));

				currentFunction.Emit (new StoreWord (tR, vR));

				PushRegister ();
			}
		}

		public override void Accept (UnaryExpression unaryExpression)
		{
			switch (unaryExpression.Operation) {
			case UnaryOperation.Reference:
				IdentifierExpression expr = unaryExpression.Value as IdentifierExpression;

				if (expr != null) {
					if (!symbolTable.IsGlobal (expr.Name)) {
						currentFunction.Emit (new AddressOf (PushRegister (), symbolTable.GetSymbol (expr.Name)));
					}
				} else {
					/* Create error */
				}


				break;
			case UnaryOperation.Derefence:
				unaryExpression.Value.Visit (this);
				int src = PopRegister ();
				currentFunction.Emit (new LoadWord (PushRegister (), src));
				break;
			}
		}

		public override void Accept (ConstantExpression constantExpression)
		{
			currentFunction.Emit (new LoadRegister (PushRegister (), constantExpression.Value));
		}

		public override void Accept (IdentifierExpression identExpression)
		{
			currentFunction.Emit (new LoadLocal (PushRegister (), symbolTable.GetSymbol (identExpression.Name)));
		}

		private int PushRegister ()
		{
			return topRegister++;
		}

		private int PopRegister ()
		{
			return --topRegister;
		}
	}
}

